package org.json;

import org.junit.Before;
import org.junit.Test;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

import static java.util.Arrays.*;
import static org.junit.Assert.*;

public class JSONPathTest {
    private static final MathContext MATHS_CONTEXT = new MathContext(10, RoundingMode.HALF_UP);
    private JSONPath jsonPath;

    @Before
    public void createJSONPath() throws JSONException {
        jsonPath = new JSONPath(new JSONObject()
                .put("name1", "value1")
                .put("name2", new JSONArray(asList("monkey", "banana", "sausage")))
                .put("name3", new JSONObject()
                        .put("name4", "Elephant")
                        .put("name5", "Another elephant")
                        .put("name6", 6))
                .put("name7", JSONObject.NULL)
                .put("name8", 42.7));
    }

    @Test
    public void extractsElementsSpecifiedWithASimplePath() {
        assertEquals("Another elephant", jsonPath.get("name3/name5"));
    }

    @Test
    public void extractsElementsFromAnArrayByIndex() {
        assertEquals("banana", jsonPath.get("name2[2]"));
    }

    @Test
    public void testElementsOfEachCanonicalJsonTypeAreExtractedCorrectly() throws Exception {
        JSONPath jsonPath = new JSONPath(allCanonicals());

        assertEquals(JSONObject.class, jsonPath.get("numbers").getClass());
        assertEquals(new Integer("8"), jsonPath.get("numbers/anInteger"));
        assertEquals(new Double("9.99"), jsonPath.get("numbers/aDouble"));
        assertEquals(new Double("7.6526e-5"), jsonPath.get("numbers/anExponent"));
        assertEquals("sausage", jsonPath.get("string"));
        assertEquals(true, jsonPath.get("yep"));
        assertEquals(false, jsonPath.get("nope"));
        assertEquals(JSONObject.NULL, jsonPath.get("nuttin"));
        assertEquals(new JSONArray("[{ age: 12, activity: \"scouts\" },{ age: 13, activity: \"guides\" }]").toString(), jsonPath.get("kids").toString());
    }

    private JSONObject allCanonicals() throws JSONException {
        return new JSONObject("" +
                "{" +
                "   numbers: {" +
                "       aDouble: 9.99," +
                "       anInteger: 8," +
                "       anExponent: 7.6526e-5" +
                "   }," +
                "   kids: [" +
                "       { age: 12, activity: \"scouts\" }," +
                "       { age: 13, activity: \"guides\" }" +
                "   ]," +
                "   string: \"sausage\"," +
                "   yep: true," +
                "   nope: false," +
                "   nuttin: null" +
                "}"
        );
    }

    @Test
    public void canonicalJsonTypesAreInferred() throws Exception {
        JSONPath jsonPath = new JSONPath(allCanonicals());

        JSONObject expectedJSONObject = new JSONObject("" +
                "{" +
                "    aDouble: 9.99," +
                "    anInteger: 8," +
                "    anExponent: 7.6526e-5" +
                "}");

        JSONArray expectedJSONArray = new JSONArray("" +
                "[" +
                "   { age: 12, activity: \"scouts\" }," +
                "   { age: 13, activity: \"guides\" }" +
                "]");

        JSONObject jsonObject = jsonPath.get("numbers");
        JSONArray jsonArray = jsonPath.get("kids");
        Integer integer = jsonPath.get("numbers/anInteger");
        Double aDouble = jsonPath.get("numbers/aDouble");
        String string = jsonPath.get("string");
        Boolean aBoolean = jsonPath.get("yep");

        assertEquals(expectedJSONObject.toString(), jsonObject.toString());
        assertEquals(expectedJSONArray.toString(), jsonArray.toString());
        assertEquals(8, integer.intValue());
        assertEquals(9.99, aDouble, 0.01);
        assertEquals("sausage", string);
        assertEquals(true, aBoolean);
    }

    @Test
    public void convertsIntegersDoublesAndSuitableStringsIntoBigDecimalValues() throws Exception {
        jsonPath = new JSONPath(new JSONObject("" +
                "{" +
                "    aDouble: 9.99," +
                "    anInteger: 8," +
                "    anExponent: 7.6526e-5," +
                "    aString: \"123.45678e-4\"" +
                "}"));

        assertEquivalent("9.99", jsonPath.getBigDecimal("aDouble"), MATHS_CONTEXT);
        assertEquivalent("8", jsonPath.getBigDecimal("anInteger"), MATHS_CONTEXT);
        assertEquivalent("7.6526e-5", jsonPath.getBigDecimal("anExponent"), MATHS_CONTEXT);
        assertEquivalent("123.45678e-4", jsonPath.getBigDecimal("aString"), MATHS_CONTEXT);
    }

    private void assertEquivalent(String expected, BigDecimal actual, MathContext mathsContext) {
        assertEquals(0, new BigDecimal(expected).round(mathsContext).compareTo(actual.round(mathsContext)));
    }

    @Test
    public void failsNoisilyIfTheElementIsNotFound() {
        try {
            new JSONPath("{small:carrot}").get("GiantTurnip");
            fail("Expected exception");
        } catch (Exception e) {
            assertEquals("Could not find: GiantTurnip", e.getMessage());
        }
    }

    @Test
    public void pathsWhichOvershootAllElementsFailNoisilyToo() throws Exception {
        jsonPath = new JSONPath(new JSONObject("" +
                "{" +
                "   monkey: {" +
                "       banana: \"Elephant\"" +
                "   }" +
                "}"
        ));

        try {
            jsonPath.get("monkey/banana/eat/now");
            fail("Expected exception");
        } catch (NodeNotFoundException e) {
            assertEquals("Could not find: eat", e.getMessage());
        }
    }

    @Test
    public void testWeCannotFindElementsWithPathWildcards() throws Exception {
        try {
            jsonPath.get("//something");
            fail("Expected exception");
        } catch (UnsupportedOperationException e) {
            assertEquals("Path wildcard '//' not (yet) supported.", e.getMessage());
        }
    }

    @Test
    public void ignoresLeadingSlashes() throws Exception {
        assertEquals("Elephant", jsonPath.get("/name3/name4"));
    }

    @Test
    public void weCanTellIfANodeExists() throws Exception {
        JSONPath jsonPath = new JSONPath(new JSONObject("{ something: 9.99 }"));

        assertTrue(jsonPath.has("something"));
        assertFalse(jsonPath.has("something/else"));
        assertFalse(jsonPath.has("nothing"));
    }

    @Test
    public void weThrowAClearExceptionWhenThePathIsTooLong() throws Exception {
        JSONPath jsonPath = new JSONPath(new JSONObject("{ something: 9.99 }"));
        try {
            jsonPath.get("something/else/again");
            fail("exception expected");
        } catch (NodeNotFoundException e) {
            assertEquals("Could not find: else", e.getMessage());
        }
    }

    @Test
    public void weThrowAClearExceptionWhenArrayIndicesAreNotProvided() throws Exception {
        JSONPath jsonPath = new JSONPath(new JSONObject("{ something: [ {value: 9.99, currency: CHF}, {value: 8.88, currency: EUR} ] }"));
        try {
            jsonPath.get("something/value");
            fail("exception expected");
        } catch (UnindexedArrayException e) {
            assertEquals("Please provide an index for the JSONArray at: something", e.getMessage());
        }
    }

    @Test
    public void weDoNotSupportDateParsing() throws Exception {
        try {
            jsonPath.getDate("aDate");
            fail("exception expected");
        } catch (UnsupportedOperationException e) {
            assertEquals("Date parsing not supported. Use JodaTime; any other option is hell.", e.getMessage());
        }
    }
}